//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.eResearch.Execution
{
    using System;
    using System.CodeDom.Compiler;
    using System.Collections.ObjectModel;
    using System.ComponentModel.Design;
    using System.ComponentModel.Design.Serialization;
    using System.IO;
    using System.Reflection;
    using System.Text;
    using System.Workflow.Activities;
    using System.Workflow.ComponentModel.Compiler;
    using System.Workflow.ComponentModel.Serialization;
    using System.Xml;
    using Microsoft.Research.ScientificWorkflow.TridentComposer;
    using Microsoft.Research.ScientificWorkflow.TridentModel;
    using Microsoft.Research.DataLayer;
    using Microsoft.Research.eResearch.Common;
    using Microsoft.Research.eResearch.Common.Linq;
    using Microsoft.Research.eResearch.Common.Validation;
    using CompModel = System.Workflow.ComponentModel;
    using System.Collections.Generic;

    /// <summary>
    /// This method class contains logic to generate the component model activity from ER activity.
    /// </summary>
    internal class WorkflowActivityModel : JobBase
    {
        /// <summary>
        /// Path of the lib folder inside the sandbox.
        /// </summary>
        private string libFolder;

        /// <summary>
        /// Collection to store the loaded assemblies.
        /// </summary>
        private Collection<Assembly> loadedAssemblies = new Collection<Assembly>();

        /// <summary>
        /// Instance of the component model activity.
        /// </summary>
        private CompModel.Activity rootActivity;

        /// <summary>
        /// Initializes a new instance of the WorkflowActivityModel class.
        /// </summary>
        /// <param name="jobId">Id of the workflow job.</param>
        /// <param name="libFolder">Path to the lib folder of the sandbox.</param>
        private WorkflowActivityModel(Guid jobId, string libFolder, bool isLightWeightExecution)
            : base(jobId, isLightWeightExecution)
        {
            this.libFolder = libFolder;
            this.XomlPath = Path.Combine(libFolder, jobId.ToString() + ".xoml");
        }

        /// <summary>
        /// Gets the path of the XOML.
        /// </summary>
        public string XomlPath { get; private set; }

        /// <summary>
        /// Gets the collection of loaded assemblies.
        /// </summary>
        public Collection<Assembly> LoadedAssemblies
        {
            get { return this.loadedAssemblies; }
        }

        /// <summary>
        /// Creates a new instance of WorkflowActivityModel with the parameters.
        /// Then builds the ComponentModel.Activity structure from ER activity.
        /// ComponentModel.Activity is serialized into XOML.
        /// XOML is compiled to worfklow activity assembly.
        /// </summary>
        /// <param name="jobId">Id of the workflow job.</param>
        /// <param name="libFolder">Path to the lib folder of the sandbox.</param>
        /// <returns>Type object of the compiled workflow.</returns>
        public static WorkflowActivityModel Generate(Guid jobId, string libFolder, bool isLightWeightExecution)
        {
            // Validate arguments
            CustomValidator.AssertNotNull(jobId, "jobId");
            CustomValidator.AssertNotEmpty(libFolder, "libFolder");
            CustomValidator.AssertRule<string>(path => !Directory.Exists(path), libFolder, Messages.PathNotExists(libFolder));

            WorkflowActivityModel model = null;

            ExceptionHandler.Handle(
            PolicyName.LoggingRethrowPolicy,
            () =>
            {
                // Create a new instance of the WorkflowActivityModel.
                model = new WorkflowActivityModel(jobId, libFolder, isLightWeightExecution);

                // Load the downloaded assemblies into AppDomain.
                model.LoadDownloadedAssemblies();

                // Build the component model activity.
                model.BuildComponentModelActivity();

                // Serialize the workflow to XOML.
                model.SerializeWorkflow();
            },
            () =>
            {
                // Close the connection.
                model.CloseRegistryConnection();
            });

            return model;
        }

        /// <summary>
        /// Merge the workflow values with the ones from the Job Required parameters.
        /// </summary>
        /// <param name="rootWorkflowModel">The workflow model.</param>
        /// <param name="workflowJob">The workflow job instance.</param>
        private static void SetWorkflowParameters(TridentWorkflowModel rootWorkflowModel, Job workflowJob)
        {
            ActivityInstanceParameterCollection<ActivityInstance> reqParameters =
                workflowJob.ActivityInstances[0].ActivityInstanceParameters;

            // Iterate through the parameters.
            foreach (ActivityInstanceParameter parameter in reqParameters)
            {
                if (!rootWorkflowModel.ModelsHashtable.ContainsKey(parameter.ParameterAssignment.Instance.Name))
                {
                    continue;
                }

                // Get the activity to which the parameter belongs to.
                BaseModel activityModel = rootWorkflowModel.ModelsHashtable[parameter.ParameterAssignment.Instance.Name] as BaseModel;

                if (activityModel != null)
                {
                    foreach (ParameterDescriptionModel paramModel in activityModel.InputParameters)
                    {
                        // If the property name matches then update the value.
                        if (paramModel.PropertyName.Equals(parameter.ParameterAssignment.ActivityParameter.Name, StringComparison.Ordinal) &&
                            paramModel.DataType.Equals(parameter.ParameterAssignment.ActivityParameter.Type, StringComparison.Ordinal))
                        {
                            paramModel.Value = parameter.ParamValue;
                            break;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Loads the downloaded assemblies into app domain from lib folder.
        /// </summary>
        private void LoadDownloadedAssemblies()
        {
            loadedAssemblies.Clear();

            // Note: The following two assemblies need to be loaded explicitly for the convertion code to work.
            loadedAssemblies.Add(typeof(DelayActivity).Assembly);
            loadedAssemblies.Add(typeof(CompModel.Activity).Assembly);

            Directory.GetFiles(libFolder, "*.dll")
                .ForEach(file =>
                {
                    ExceptionHandler.Handle(
                        PolicyName.LoggingPolicy,
                        () =>
                        {
                            loadedAssemblies.Add(Assembly.Load(Path.GetFileNameWithoutExtension(file)));
                        });
                });

            Directory.GetFiles(libFolder, "*.exe")
                .ForEach(file =>
                {
                    ExceptionHandler.Handle(
                        PolicyName.LoggingPolicy,
                        () =>
                        {
                            loadedAssemblies.Add(Assembly.Load(Path.GetFileNameWithoutExtension(file)));
                        });
                });
        }

        /// <summary>
        /// Builds the component model activity from ER activity.
        /// </summary>
        private void BuildComponentModelActivity()
        {
            // Create an instance of the Activity composer.
            // This will be used to Generate the component model activity.
            ActivityComposer actComposer = new ActivityComposer(RegistryConnection, false);

            // Create an instance of the Worklfow composer.
            WorkflowComposer workflowComposer = new WorkflowComposer(RegistryConnection, actComposer);

            // Create the Workflow Model for the ER.Activity.
            Activity workflow = WorkflowJob.ActivityInstances[0].Activity;

            TridentWorkflowModel rootWorkflowModel = new TridentWorkflowModel(
                workflow,
                new ModelFactory(actComposer.ActivityMetadataList, actComposer.SupportedActivityCollection));

            // Set workflow parameters
            WorkflowActivityModel.SetWorkflowParameters(rootWorkflowModel, this.WorkflowJob);

            if (workflow.IsBlackbox)
            {
                // Get the reference activity.
                CompModel.Activity originalWorkflow = workflowComposer.GetActualWorkflow(workflow);

                rootWorkflowModel.BlackBoxActivity = originalWorkflow;

                // Validate bindings and remove which are not valid.
                rootWorkflowModel.ValidateBlackboxBindings();

                // Create the componentModel.Activity.
                rootActivity = workflowComposer.CreateCompModelActivity(
                    rootWorkflowModel.Root as CompositeActivityModel,
                    originalWorkflow,
                    loadedAssemblies);
            }
            else
            {
                // Create the componentModel.Activity.
                rootActivity = actComposer.CreateCompModelActivity(rootWorkflowModel.Root as CompositeActivityModel, loadedAssemblies);
            }

            // Update the Parameters to the activity model.
            actComposer.UpdateParameter(rootActivity, rootWorkflowModel.Root, true);
        }

        /// <summary>
        /// Serializes the workflow instance to XOML.
        /// </summary>
        private void SerializeWorkflow()
        {
            using (CustomWorkflowSerializer serializer = new CustomWorkflowSerializer(rootActivity, XomlPath, loadedAssemblies))
            {
                serializer.Serialize();
            }
        }
    }
}